---
title: 第一个MyBatis程序
---

## 搭建环境

### 创建数据库

```sql
CREATE
DATABASE mybatis;
USE
mybatis;
CREATE TABLE user
(
    id       INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(32),
    password VARCHAR(32),
    address  VARCHAR(32)
);
INSERT INTO user(username, password, address)
VALUES ('zhangsan', '123456', '北京'),
       ('lisi', '123456', '上海'),
       ('wangwu', '123456', '广州');
```

### 新建 maven 项目

在 IDEA 中新建一个 maven 项目，删除 src 目录，将其作为父工工程，然后导入依赖。

```pom
<dependencies>
    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.4</version>
    </dependency>
    <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.20</version>
    </dependency>
    <!--junit-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>
```

### 创建一个模块

在 IDEA 中新建一个模块，将其作为子工程，然后在子工程中创建一个 resources 目录，然后在 resources 目录下创建一个
mybatis-config.xml 文件，内容如下：

```xml

<configuration>
    <environments default="development">
        <environment id="dev">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"></property>
                <property name="url"
                          value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;serverTimezone=UTC&amp;useUnicode=true"></property>
                <property name="username" value="root"></property>
                <property name="password" value="Zbb010719!"></property>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="pub/wkq/dao/UserMapper.xml"></mapper>
    </mappers>
</configuration>
```

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过
SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出
SqlSessionFactory 的实例。

从 XML 文件构建 SqlSessionFactory 的实例非常简单，建议使用类路径下的资源文件进行配置。但也可以使用任意的输入流（InputStream）实例，比如用文件路径字符串或
file:// URL 构造的输入流。

MyBatis 包含一个名叫做 Resources 的工具类，可以从 classpath 或其它位置加载资源文件。

```java
public class MyBatisUtils {
    static {
        try {
            //使用 MyBatis 的第一步：获取 SqlSessionFactory 对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
```

xml 配置文件（configuration xml） 中包含了对 MyBatis
系统的核心设置，包含获取数据库连接实例的数据源（DataSource）和决定事务作用域和控制方式的事务管理器（TransactionManager）。

### 从 SqlSessionFactory 中获取 SqlSession

有了 SqlSessionFactory，我们就可以从中获取 SqlSession 的实例了。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。

```java
public class MyBatisUtils {
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            //使用 MyBatis 的第一步：获取 SqlSessionFactory 对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession();
    }
}
```

### 编写代码

#### 创建实体类

```java User.java
/**
 * <h1>用户类</h1>
 * 属性有：主键、用户名、密码、地址
 */
@Data
public class User {
    /**
     * 主键
     */
    private int id;
    /**
     * 用户名
     */
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 地址
     */
    private String address;
}
```

#### Dao 接口

```java
public interface UserDao {
    /**
     * 查询所有用户
     * @return
     */
    List<User> findAll();
}
```

这里给出一个基于 XML 映射语句的示例，可以使用 SqlSession 进行调用。

```xml 接口实现类由mapper文件代替
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--命名空间会绑定一个对应的Dao/Mapper接口,相当于实现类-->
<mapper namespace="pub.wkq.dao.UserDao">
    <!--id:唯一标识,对应方法名字-->
    <select id="getUserList" resultType="pub.wkq.pojo.User">
        select *
        from user
    </select>
</mapper>
```

命名空间的作用有两个：

1. 为了解决重名问题
2. 接口绑定

:::tip 命名解析
为了减少输入量，MyBatis 对所有的命名配置元素（包括语句、结果集和参数映射）进行了简写。这些简写的规则如下：

* 完全限定名（比如 “pub.wkq.dao.UserDao”）将直接用于查找及使用。
* 短名称（比如 “UserDao”）如果全局唯一也可以作为一个单独的引用。如果不唯一，就会导致错误。
  :::

#### 测试

:::tip

```xml
 <!--在 build 中配置 resources ，来防止资源导出失败的问题-->

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>
```

:::

```java
package pub.wkq.dao;

import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import pub.wkq.pojo.User;
import pub.wkq.utils.MyBatisUtils;

import java.util.List;

public class UserDaoTest {
    @Test
    public void testGetUserList() {
        // 第一步：获取 SqlSession 对象
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        //方式一：getMapper
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        userDao.getUserList().forEach(System.out::println);
        System.out.println(" ======================= ");
        //方式二：这种方式不推荐使用
        List<User> userList = sqlSession.selectList("pub.wkq.dao.UserDao.getUserList");
        userList.forEach(System.out::println);
        //关闭 SqlSession
        sqlSession.close();
    }
}
```

## 三个核心类

### SqlSessionFactoryBuilder

这个类可以被实例化、使用和丢弃，一旦创建了 SqlSessionFactory，就不再需要它了。因此 SqlSessionFactoryBuilder
实例的最佳作用域是方法作用域（也就是局部方法变量）。

可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例，但是最好还是不要让其一直存在，以保证所有的 XML
解析资源可以被释放给更重要的事情。

### SqlSessionFactory

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在，没有任何理由对它进行清除或重建。使用 SqlSessionFactory
的最佳实践是在应用运行期间不要重复创建多次，多次重建 SqlSessionFactory 被视为一种代码“坏味道（bad smell）”。因此
SqlSessionFactory 的最佳作用域是应用作用域。有很多方法可以做到，最简单的就是使用单例模式或者静态单例模式。

### SqlSession

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的，因此是不能被共享的，
所以它的最佳的作用域是请求或方法作用域。绝对不能将 SqlSession 实例的引用放在一个类的静态字段甚至是实例字段中。
也绝不能将 SqlSession 实例的引用放在任何类型的管理作用域中，比如 Servlet 架构中的 HttpSession。如果你现在正在使用一种 Web
框架，
考虑将 SqlSession 放在一个和 HTTP 请求对象相似的作用域中。换句话说，每次收到的 HTTP 请求，就可以打开一个 SqlSession，
返回一个响应，就关闭它。这个关闭操作是很重要的，你应该把这个关闭操作放到 finally 块中以确保每次都能执行关闭。下面的示例就是一个确保
SqlSession 关闭的标准模式：

```java
try(SqlSession session=sqlSessionFactory.openSession()){
        // 你的应用逻辑代码
        }
```
